package yagwl.service.demo;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import yagwl.service.util.db.DaoContext;
import yagwl.service.util.svc.BaseException;
import yagwl.service.util.svc.BaseException.ErrorCode;

public class ProductCategoryDao {

  private static final Logger log = Logger.getLogger(ProductCategoryDao.class
      .getName());

  private DaoContext ctx;

  public ProductCategoryDao(DaoContext ctx) {
    this.ctx = ctx;
  }

  public ProductCategoryEntity getCategoryTree() {
    Query q = ctx.em().createQuery("select from ProductCategoryEntity "
        + "where parentProductCategoryId is null");
    ProductCategoryEntity rootCat;
    try {
      rootCat = (ProductCategoryEntity) q.getSingleResult();
    } catch (NoResultException e) {
      doBootStrap();
      rootCat = (ProductCategoryEntity) q.getSingleResult();
    }
    getCategoryRecursive(rootCat);
    return rootCat;
  }

  private void getCategoryRecursive(ProductCategoryEntity cat) {
    EntityManager em = ctx.em();
    Query q = em
        .createQuery(
            "select from ProductCategoryEntity where parentProductCategoryId = :parentId")
        .setParameter("parentId", cat.getProductCategoryId());
    List<ProductCategoryEntity> list = q.getResultList();
    if (list.size() > 0)
      cat.setChildren(new ArrayList<ProductCategoryEntity>());
    for (ProductCategoryEntity child : list) {
      log.info(cat.getProductCategoryId() + ":" + child.getProductCategoryId());
      cat.getChildren().add(child);
      getCategoryRecursive(child);
    }

    for (Long prdId : cat.getProductIds()) {
      if (cat.getProducts() == null)
        cat.setProducts(new ArrayList<ProductEntity>());
      ProductEntity prd = em.find(ProductEntity.class, prdId);
      // remove references to deleted products
      if (prd == null) {
        log.warning("Non-existing reference: " + cat.getProductCategoryId()
            + ":" + prdId + "; removing reference");
        removeProduct(cat.getProductCategoryId(), prdId);
      } else
        cat.getProducts().add(prd);
    }
  }

  public ProductCategoryEntity createCategory(ProductCategoryEntity cat) {
    if (cat.getParentProductCategoryId() == null) {
      throw new BaseException(ErrorCode.REQUIRED_FIELD_MISSING);
    }
    EntityManager em = ctx.em();
    // TODO P2 validate here that parent exists
    try {
      em.getTransaction().begin();
      em.persist(cat);
      em.getTransaction().commit();
    } catch (Exception e) {
      log.severe(e.toString());
      em.getTransaction().rollback();
      throw new BaseException(ErrorCode.INTERNAL_TRANSACTION_ERROR, e);
    }
    return cat;
  }

  /**
   * updates attributes of a product category object. parent id should be passed
   * children not to be passed
   */
  public void updateCategory(ProductCategoryEntity cat) {
    EntityManager em = ctx.em();
    try {
      ProductCategoryEntity origCat = em.find(ProductCategoryEntity.class, cat
          .getProductCategoryId());
      cat.setProductIds(origCat.getProductIds());
      em.getTransaction().begin();
      em.persist(cat);
      em.getTransaction().commit();
    } catch (Exception e) {
      log.severe(e.toString());
      em.getTransaction().rollback();
      throw new BaseException(ErrorCode.INTERNAL_TRANSACTION_ERROR, e);
    }
  }

  public void deleteCategorySubTree(Long categoryId) {
    EntityManager em = ctx.em();
    deleteCategoryRecursive(em, categoryId);
    em.getTransaction().begin();
    Query delq = em
        .createQuery(
            "delete from ProductCategoryEntity where productCategoryId = :categoryId")
        .setParameter("categoryId", categoryId);
    log.info("Deleting: " + categoryId);
    delq.executeUpdate();
    em.getTransaction().commit();
  }

  private void deleteCategoryRecursive(EntityManager em, Long categoryId) {
    log.info("Entering: " + categoryId);
    Query q = em.createQuery(
        "select from ProductCategoryEntity "
            + "where parentProductCategoryId = :categoryId").setParameter(
        "categoryId", categoryId);
    List<ProductCategoryEntity> list = q.getResultList();

    for (ProductCategoryEntity child : list) {
      deleteCategoryRecursive(em, child.getProductCategoryId());
      em.getTransaction().begin();
      Query delq = em.createQuery(
          "delete from ProductCategoryEntity where productCategoryId = :id")
          .setParameter("id", child.getProductCategoryId());
      log.info("Deleting: " + categoryId + ":" + child.getProductCategoryId());
      delq.executeUpdate();
      em.getTransaction().commit();
    }
  }

  public void addProduct(Long categoryId, Long productId) {
    log.info(categoryId + ":" + productId);
    EntityManager em = ctx.em();
    ProductCategoryEntity cat = em
        .find(ProductCategoryEntity.class, categoryId);
    try {
      em.getTransaction().begin();
      if (cat.getProductIds() == null)
        cat.setProductIds(new ArrayList<Long>());
      cat.getProductIds().add(productId);
      em.getTransaction().commit();
    } catch (Exception e) {
      log.severe(e.toString());
      em.getTransaction().rollback();
      throw new BaseException(ErrorCode.INTERNAL_TRANSACTION_ERROR, e);
    }
  }

  public void removeProduct(Long categoryId, Long productId) {
    EntityManager em = ctx.em();
    ProductCategoryEntity cat = em
        .find(ProductCategoryEntity.class, categoryId);
    if (cat.getProductIds() == null) {
      log.warning("No children");
      return;
    }
    try {
      em.getTransaction().begin();
      cat.getProductIds().remove(productId);
      em.getTransaction().commit();
    } catch (Exception e) {
      log.severe(e.toString());
      em.getTransaction().rollback();
      throw new BaseException(ErrorCode.INTERNAL_TRANSACTION_ERROR, e);
    }
  }

  private void doBootStrap() {
    log.config("bootstrapping");
    EntityManager em = ctx.em();
    try {
      em.getTransaction().begin();
      ProductCategoryEntity cat = new ProductCategoryEntity();
      cat.setName("Root");
      em.persist(cat);
      em.getTransaction().commit();
    } catch (Exception e) {
      log.severe(e.toString());
      em.getTransaction().rollback();
      throw new BaseException(ErrorCode.INTERNAL_TRANSACTION_ERROR, e);
    }
  }

}
